iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0

https://ithelp.ithome.com.tw/upload/images/20210926/20139359tu5MSeiweV.png

接下來的篇章我們會把目光聚焦於 interfaces/ 這個目錄底下的內容,想確認 slate package 完整的 src directory 的讀者們可以回到 上一篇 做確認。

slate Interfaces


我們首先來為它底下的 files 做點簡單的分類與介紹:

https://ithelp.ithome.com.tw/upload/images/20210926/20139359by7pwbU5II.png

除了 custom-types.ts 是專門定義與處理 type extension 之外,其他的 file 都各自代表著一組 concept ,也就是上圖畫了紅線的部分。它們各自同時擁有:定義這組 concept 相關的 types 、這組 concept 提供給開發者使用的 method apis。

我們用一個名叫 Example 的範例來講解 concept file 裡基本的 code-structure :

export type ExampleType = {
	// type declaration, use "interface" to declare it if it's extendable
}

export interface ExampleInterface {
	// Concept method apis interface declaration
}

export const Example: ExampleInterface = {
	// Concept methods implementation
}

如果 type 被定義為 extendable 的話,我們則能看到額外引用 custom-types.ts 裡的 ExtendedType type utility 去擴充其基本 BaseType 的 type 。

type 來 type 去的是想亂死誰,直接上個範例最清楚,一樣用 Example 來介紹。

import { ExtendedType } from './custom-types';

export interface BaseExample {
  // type declaration
}

export type Example = ExtendedType<'Example', BaseExmaple>

要注意其實上面的範例放進 ExtendedType 是不過關的,因為 ExtendedType 吃得第一個 string generic 是有用一個 Union type 限制住的,詳情我們後續在 custom-type 的篇章會再深入講解,目前只是先讓讀者們有個概念而已。


接著我們看到有被黃框的 files ,這些是我們在建立 editor 與操作 editor value 時主要會使用到的概念,我們接下來會花不少的篇幅,拿官方提供的註解當溫開水配著服用,依序介紹裡面的 types ,而各個 files 裡提供的 method apis 因為數量眾多又雜又亂,一條一條介紹完另一個 30 天就又過去了所以我們就不在這邊細說,筆者的時間跟心態都算充裕的話再另外整理成 reference 提供給各位吧~

那麼就如標題所言,我們首先會針對 slate 的 document model 所需要用到的 concepts 做介紹,分別是: text 、 element。

https://ithelp.ithome.com.tw/upload/images/20210926/20139359GBkAbKAyk2.png

text.ts


這裡面就只有一個 Extendable 的 Text type 而已,光看名字應該也能很直覺的反應出它代表的就是 editor 裡的『文字資料』。

/**
 * `Text` objects represent the nodes that contain the actual text content of a
 * Slate document along with any formatting properties. They are always leaf
 * nodes in the document tree as they cannot contain any children.
 */

export interface BaseText {
  text: string
}

export type Text = ExtendedType<'Text', BaseText>

Text type 只規定了其中的內容物必須要有一個類別為 string 的 text property ,開發者可以依照自己的喜好定義其他的 properties ,就拿我們在 Day3 舉過官方範例來看看。

下圖只要有被紅圈圈到的 Object 都各為一組 Text

https://ithelp.ithome.com.tw/upload/images/20210926/20139359zAomlQFvmw.png

再來看看它的 module declaration

type CustomText = {
  bold?: boolean
  italic?: boolean
  code?: boolean
  text: string
}

// slate module "Text" declaration
declare module 'slate' {
  interface CustomTypes {
    Text: CustomText
  }
}

把定義好的 module 引入就能使用自定義的 CustomText 隨意開發了,就是這麼輕鬆寫意。


Text 提供的 method apis 裡有一個特別值得提到的 method 是 decorations()

/**
 * Get the leaves for a text node given decorations.
 */
decorations(node: Text, decorations: Range[]): Text[] {
    ... // method implementation
}

Range 的概念我們會在 下一篇 介紹,目前讀者先知道它是 Slate 中描述一段可以橫跨不同的 Text 甚至 Element 的『字元集範圍』就好,一個 Range 裡面會同時有 anchor 與 focus 這兩個 properties 代表這個 range 的起始字元位置以及結束字元位置。

當我們丟給 decorations 合法的 Text 以及 range list ,也就是這些 Ranges 的範圍是正確的位於該 Text 內時,它會動態地幫我們依照這些 Range 拆成一組 Text list。

const text = { text: 'This is a text example.' };
const ranges = [{
    anchor: { path: [0, 0], offset: 5 },
    focus: { path: [0, 0], offset: 7 },
}];

/**
returns: [
    { text: 'This ' },
    { text: 'is' },
    { text: ' a text example.' },
] */
Text.decorations(text, ranges);

當然你也可以放上自定義的 Text property

const text = { text: 'This is text example2.' };
const ranges = [{
    anchor: { path: [0, 0], offset: 5 },
    focus: { path: [0, 0], offset: 7 },
    bold: true,
}];

/**
returns: [
    { text: 'This ' },
    { text: 'is', bold: true },
    { text: ' text example2.' },
] */
Text.decorations(text, ranges);

嗯?幹嘛多此一舉?直接修改 document model 裡的 value 不就好了嗎?


筆者一開始也很困惑於這個功能存在的意義,確實直接修改 value 就能達到一模一樣的效果了,後來在官方提供的 Search Highlighting example 裡找到了答案:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/a2cd3927-6885-4be1-b0cd-b7f025568edc/ezgif.com-gif-maker拷貝.gif

它很適合用於開發 「修改的頻率高,幅度大,卻只修正 Text properties 而不會更改到文字的內容或其他 value 的功能」。正如其名 decorations ,它的工用就像是在整個工程的尾端輕輕的裝飾文字的屬性,因為不會去更動到 editor value ,所以可以省略掉許多更新整個 value tree 龐雜的運算,因此把它放在 view layer 的淺層,動態地計算 decorate 後的 Text ,會讓速度整個快上非常非常多。(讀者可以自己去實驗看看 XD


element.ts


裡面主要定義的 type 也只有一個 Extendable 的 Element type 而已,slate 的 document model 就是由 Element 與 Text 這兩種 types 所組成的,相對於 Text 所代表的文字資料, Element 代表 editor 裡的元件資料

/**
 * `Element` objects are a type of node in a Slate document that contain other
 * element nodes or text nodes. They can be either "blocks" or "inlines"
 * depending on the Slate editor's configuration.
 */

export interface BaseElement {
  children: Descendant[]
}

export type Element = ExtendedType<'Element', BaseElement>

Element type 只規定了其中的內容物必須要有一個類別為 children 的 Descendant[] property ,開發者一樣可以依照自己的喜好定義其他的 properties 。

Descendant type 象徵著 slate document tree 裡的子節點,我們會在之後的 node concept 章節 詳細介紹,我們現在先偷瞄一眼 Descendant type 的定義

/**
 * The `Descendant` union type represents nodes that are descendants in the
 * tree. It is returned as a convenience in certain cases to narrow a value
 * further than the more generic `Node` union.
 */

export type Descendant = Element | Text

可以看到 Element 與 Descendant 這兩個 type 是彼此關聯的,在 Element 的 children property 裡再放上另一個 Element 也是沒問題的,這也是為什麼開發者可以很直觀地透過 slate 建立擁有巢狀結構的 feature ,只要照著 type 的規範走 slate 完全不在乎你的 document tree 長什麼樣子,你愛怎麼巢就怎麼巢。

另外我們也可以在 Element type 的註解裡看到,開發者也能針對各個不同的 elements 定義 blocks 或 inlines 屬性,這部分因為牽涉到 slate editor 的 config 定義,我們就放到後面介紹 editor concept 的篇章吧!

element.ts 裡還有一個附加的 type 是 ElementEntry ,它主要是用在進行 iterate 相關操作的用途上,關於 iterate 我們之後會在整理出一篇文章跟各位分享分享。

哇今天的內容不少誒!來稍微整理一下:我們首先對 interfaces/ 這個目錄底下的 files 做了個簡單的分類 → 定義畫了 紅線 的 concept files 裡的 code structure → 分別介紹了 Document model 裡會出現的 Text type 以及 decorations methods 、 Element type 以及它與 Descendant type 的關係


感謝你的整理,今天的篇幅有那麼一點長,請讀者花些時間消化一下,緊接著到下一篇我們要介紹 slate 是如何實現 document value 的『定位』這件事的。

咱們一樣下一篇見啦~


上一篇
Day 10. slate × 架構藍圖
下一篇
Day 12. slate × Interfaces × Data-Model
系列文
深入 slate.js x 一起打造專屬的富文字編輯器吧! 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言